<!DOCTYPE html>
<html lang="en">
  
  <head>
    <meta charset="UTF-8">
    <title>Bubbles</title>
    <style>
      .g6-tooltip {
        border: 1px solid #e2e2e2;
        border-radius: 4px;
        font-size: 12px;
        color: #545454;
        background-color: rgba(255, 255, 255, 0.9);
        padding: 10px 8px;
        box-shadow: rgb(174, 174, 174) 0px 0px 10px;
      }
    </style>
  </head>
  
  <body>
    <div id="tip">
    </div>
    <div id="mountNode"></div>
    <script src="../build/g6.js"></script>
    <script src="./assets/jquery-3.2.1.min.js"></script>
    <script>
      const colors = [ 'rgb(64, 174, 247)', 'rgb(108, 207, 169)', 'rgb(157, 223, 125)',
      'rgb(240, 198, 74)' ];

      const data = {
        "nodes": [{
          "id": "0",
          "label": "0",
          "value": 10,
          "cluster": "a",
          "description": "this is node 0, \nand the value of it is 10"
        },
        {
          "id": "1",
          "label": "1",
          "value": 20,
          "cluster": "b",
          "description": "this is node 1, \nand the value of it is 20"
        },
        {
          "id": "2",
          "label": "2",
          "value": 5,
          "cluster": "a",
          "description": "this is node 2, \nand the value of it is 5"
        },
        {
          "id": "3",
          "label": "3",
          "value": 10,
          "cluster": "a",
          "description": "this is node 3, \nand the value of it is 10"
        },
        {
          "id": "4",
          "label": "4",
          "value": 12,
          "cluster": "c",
          "subCluster": "sb",
          "description": "this is node 4, \nand the value of it is 12"
        },
        {
          "id": "5",
          "label": "5",
          "value": 18,
          "cluster": "c",
          "subCluster": "sa",
          "description": "this is node 5, \nand the value of it is 18"
        },
        {
          "id": "6",
          "label": "6",
          "value": 3,
          "cluster": "c",
          "subCluster": "sa",
          "description": "this is node 6, \nand the value of it is 3"
        },
        {
          "id": "7",
          "label": "7",
          "value": 7,
          "cluster": "b",
          "subCluster": "sa",
          "description": "this is node 7, \nand the value of it is 7"
        },
        {
          "id": "8",
          "label": "8",
          "value": 21,
          "cluster": "d",
          "subCluster": "sb",
          "description": "this is node 8, \nand the value of it is 21"
        },
        {
          "id": "9",
          "label": "9",
          "value": 9,
          "cluster": "d",
          "subCluster": "sb",
          "description": "this is node 9, \nand the value of it is 9"
        }],
        "edges": []
      };

      const graph = new G6.Graph({
        container: 'mountNode',
        width: 1000,
        height: 800,
        layout: {
          type: 'force',
          nodeStrength: 30,
          collideStrength: 0.7,
          alphaDecay: 0.01,
          preventOverlap: true,
        },
        modes: {
          default: [ 'drag-node' ]
        },
        defaultNode: {
          size: [ 10, 10 ],
          color: 'steelblue',
          style: {
            opacity: 0.8,
            lineWidth: 1,
            stroke: '#999'
          },
        },
        defaultEdge: {
          size: 1,
          color: '#e2e2e2'
        },
        nodeStateStyles: {
          click: {
            opacity: 1,
          }
        }
      });

      function refreshDragedNodePosition(e) {
        const model = e.item.get('model');
        model.fx = e.x;
        model.fy = e.y;
      }
      graph.on('node:dragstart', e => {
        graph.layout();
        refreshDragedNodePosition(e);
      });
      graph.on('node:drag', e => {
        refreshDragedNodePosition(e);
      });
      graph.on('node:dragend', e => {
        e.item.get('model').fx = null;
        e.item.get('model').fy = null;
      });
      graph.on('node:click', e => {
        const node = e.item;
        const states = node.getStates();
        let clicked = false;
        const model = node.getModel();
        let size = 200;
        let labelText = 'NODE:' + model.id + '\n' + model.description;
        states.forEach(state => {
          if (state === 'click') {
            clicked = true;
            size = model.oriSize;
            labelText = model.oriLabel;
          }
        });
        graph.setItemState(node, 'click', !clicked);
        graph.updateItem(node, {
          size,
          label: labelText
        });
        graph.layout();
      });

      // mapping
      const edges = data.edges;
      const nodes = data.nodes;
      const nodeMap = new Map();
      const clusterMap = new Map();
      let clusterId = 0;
      nodes.forEach((node, i) => {
        nodeMap.set(node.id, node);
        // cluster
        if (node.cluster && clusterMap.get(node.cluster) === undefined) {
          clusterMap.set(node.cluster, clusterId);
          clusterId ++;
        }
        const cid = clusterMap.get(node.cluster);
        if (node.style) {
          node.style.fill = colors[cid % colors.length];
        } else {
          node.style = {
            fill: colors[cid % colors.length]
          }
        }
      });

      // map the value to node size
      let maxNodeValue = -9999,
      minNodeValue = 9999;
      nodes.forEach(n => {
        if (maxNodeValue < n.value) maxNodeValue = n.value;
        if (minNodeValue > n.value) minNodeValue = n.value;
      });
      const nodeSizeRange = [10, 30];
      const nodeSizeDataRange = [minNodeValue, maxNodeValue];
      scaleNodeProp(nodes, 'size', 'value', nodeSizeDataRange, nodeSizeRange);
      
      nodes.forEach(node => {
        node.oriSize = node.size;
        node.oriLabel = node.label;
      });

      graph.data(data);
      graph.render();
      function scaleNodeProp(elements, propName, refPropName, dataRange, outRange) {
        const outLength = outRange[1] - outRange[0];
        const dataLength = dataRange[1] - dataRange[0];
        elements.forEach(n => {
          if (propName.split('.')[0] === 'style') {
            if (n.style) {
              n.style[propName.split('.')[1]] = (n[refPropName] - dataRange[0]) * outLength / dataLength + outRange[0];
            } else {
              n.style = {
                [propName.split('.')[1]]: (n[refPropName] - dataRange[0]) * outLength / dataLength + outRange[0]
              }
            }
          } else {
            n[propName] = (n[refPropName] - dataRange[0]) * outLength / dataLength + outRange[0];
          }
        });
      }
    </script>
  </body>

</html>